#include "Ruler.h"

#include <QtMath>
#include <QDebug>

namespace LeGraphicsView
{

    Ruler::Ruler(QObject *parent) : QObject(parent),
        m_minimumValueStep(1.0),
        m_minimumTickSpacing(5),
        m_coarseMultiplier(10),
        m_minimumValue(0.0),
        m_maximumValue(100.0),
        m_size(0),
        m_needsUpdate(true),
        m_levelOfDetail(1.0)
    {

    }

    qreal Ruler::minimumValueStep() const
    {
        return m_minimumValueStep;
    }

    void Ruler::setMinimumValueStep(qreal minimumValueStep)
    {
        if (qFuzzyCompare(m_minimumValueStep, minimumValueStep))
        {
            return;
        }
        m_minimumValueStep = minimumValueStep;
        invalidate();
    }

    qreal Ruler::minimumValue() const
    {
        return m_minimumValue;
    }

    void Ruler::setMinimumValue(qreal minimumValue)
    {
        if (qFuzzyCompare(m_minimumValue, minimumValue))
        {
            return;
        }
        m_minimumValue = minimumValue;
        invalidate();
    }

    qreal Ruler::maximumValue() const
    {
        return m_maximumValue;
    }

    void Ruler::setMaximumValue(qreal maximumValue)
    {
        if (qFuzzyCompare(m_maximumValue, maximumValue))
        {
            return;
        }
        m_maximumValue = maximumValue;
        invalidate();
    }

    qreal Ruler::ceilValue(qreal value) const
    {
        maybeUpdate();
        auto reminder = std::fmod(value, m_minorValueStep);
        return value - reminder + m_minorValueStep;
    }

    qreal Ruler::floorValue(qreal value) const
    {
        maybeUpdate();
        auto reminder = std::fmod(value, m_minorValueStep);
        return value - reminder;
    }

    qreal Ruler::roundValue(qreal value) const
    {
        maybeUpdate();
        auto reminder = std::fmod(value, m_minorValueStep);
        if (reminder < (0.5 * m_minorValueStep))
        {
            return value - reminder;
        }
        else
        {
            return value - reminder + m_minorValueStep;
        }
    }

    QVector<qreal> Ruler::majorValues() const
    {
        maybeUpdate();
        return m_majorValues;
    }

    qreal Ruler::majorValueStep() const
    {
        maybeUpdate();
        return m_majorValueStep;
    }

    QVector<qreal> Ruler::minorValues() const
    {
        maybeUpdate();
        return m_minorValues;
    }

    qreal Ruler::minorValueStep() const
    {
        maybeUpdate();
        return m_minorValueStep;
    }

    int Ruler::minimumTickSpacing() const
    {
        return m_minimumTickSpacing;
    }

    void Ruler::setMinimumTickSpacing(int minimumTickSpacing)
    {
        if (m_minimumTickSpacing == minimumTickSpacing)
        {
            return;
        }
        m_minimumTickSpacing = minimumTickSpacing;
        invalidate();
    }

    int Ruler::coarseMultiplier() const
    {
        return m_coarseMultiplier;
    }

    void Ruler::setCoarseMultiplier(int coarseMultiplier)
    {
        if (m_coarseMultiplier == coarseMultiplier)
        {
            return;
        }
        m_coarseMultiplier = coarseMultiplier;
        invalidate();
    }

    int Ruler::size() const
    {
        return m_size;
    }

    QVector<int> Ruler::majorTicks() const
    {
        maybeUpdate();
        return m_majorTicks;
    }

    QVector<int> Ruler::minorTicks() const
    {
        maybeUpdate();
        return m_minorTicks;
    }

    int Ruler::tickPosition(qreal value) const
    {
        maybeUpdate();
        return calculateTickPosition(value);
    }

    void Ruler::setSize(int size)
    {
        if (m_size == size)
        {
            return;
        }
        m_size = size;
        invalidate();
    }

    void Ruler::invalidate() const
    {
        m_needsUpdate = true;
        emit changed();
    }

    void Ruler::maybeUpdate() const
    {
        if (!m_needsUpdate)
        {
            return;
        }

        m_levelOfDetail = m_size / qAbs(m_maximumValue - m_minimumValue);
        qreal smallestStep = qMax(m_minimumValueStep, qreal(m_minimumTickSpacing) / m_levelOfDetail);

        m_minorValueStep = normaliseValueStep(smallestStep);
        m_majorValueStep = m_minorValueStep * m_coarseMultiplier;
        updateValueSeries(m_minorValues, m_minorValueStep); // FIXME: optimise by calculating
        updateValueSeries(m_majorValues, m_majorValueStep); // FIXME: both at the same time
        updateTickSeries(m_minorTicks, m_minorValues);
        updateTickSeries(m_majorTicks, m_majorValues);

        m_needsUpdate = false;
    }

    qreal Ruler::normaliseValueStep(qreal step)
    {
        qreal exponent = qFloor(std::log10(step)/std::log10(10));
        qreal magnitude = std::pow(10.0f, exponent);
        qreal residual = step / magnitude;
        qreal factor = 1.0;
        if (residual > 5.0)
            factor = 10.0;
        else if (residual > 2.0)
            factor = 5.0;
        else if (residual > 1.0)
            factor = 2.0;
        return magnitude * factor;

    }

    int Ruler::calculateTickPosition(qreal value) const
    {
        return qCeil(m_levelOfDetail * (value - m_minimumValue) - 1.0);
    }

    void Ruler::updateValueSeries(QVector<qreal> &valueSeries, qreal step) const
    {
        if (step == 0)
        {
            return;
        }

        qreal range = qAbs(m_maximumValue - m_minimumValue);
        int steps = qCeil(range / step);

        valueSeries.resize(steps);
        if (steps == 0)
        {
            return;
        }

        qreal offset = std::fmod(m_minimumValue, step);
        for (int i = 0; i < steps; i++)
        {
            valueSeries[i] = m_minimumValue - offset + i * step;
        }
    }

    void Ruler::updateTickSeries(QVector<int> &tickSeries, const QVector<qreal> &valueSeries) const
    {
        tickSeries.resize(valueSeries.size());
        if (valueSeries.size() == 0)
        {
            return;
        }
        for (int i = 0; i < valueSeries.size(); i++)
        {
            tickSeries[i] = calculateTickPosition(valueSeries[i]);
        }
    }

}
